/* autor Konjkov.VV@gmail.com */

#define as_string(x) #x

#define SQL_UPPER "lower"

#define SYSTEM_VER "3.3"

#define DATA_VER_MAJOR "2"
#define DATA_VER_MINOR "0"

#define SQL_SYSFIELD1 "packet"
#define SQL_SYSFIELD2 "initpacket"

#define SQL_FIELDPREFIX1 "cur"
#define SQL_FIELDPREFIX_NEW "new"
#define SQL_FIELDPREFIX_OLD "old"

#define SQL_PREFIX_DELTABLE "del"
#define SQL_PREFIX_TEMPTABLE "temp"
#define SQL_PREFIX_UNLOADTABLE "unload"

#define SQL_SCHEMA "mcc"
#define THIS "azs74"

#define MAX_NAME_LEN 64
#define MAX_STATEMENT_LEN 1024

// каталог для временных файлов
#define PATH_TEMP  "/var/spool/sync"
// каталог для прочих файлов
#define PATH_ETCS  "/etc/sync"
// каталог-архив пакетов
#define PATH_ARCH  "/var/opt/arch"
// общий каталог для данных всех удаленных узлов
#define PATH_NODES "/var/spool/uucppublic"

// Вернуть список зарегистрированных в системе узлов
#define SQL_NODELIST "SELECT name FROM "SQL_SCHEMA".syncNODES ORDER BY name"

// Вернуть список временнных таблиц
#define SQL_TEMPTABLELIST "SELECT table_schema || '.' || table_name \
FROM information_schema.tables \
WHERE table_name LIKE '%_"SQL_PREFIX_TEMPTABLE"' \
OR table_name LIKE '%_"SQL_PREFIX_UNLOADTABLE"'"

// Триггер для определения номера пакета (SQL_SYSFIELD2),
// в который запись попала впервые.
#define SQL_FUNCTION_BU1 "CREATE OR REPLACE FUNCTION %s.%s_sync_func_bu1() \
RETURNS trigger AS \
\$BODY\$ \
BEGIN \
if ("SQL_FIELDPREFIX_NEW"."SQL_SYSFIELD1" <> "SQL_FIELDPREFIX_OLD"."SQL_SYSFIELD1" \
    and "SQL_FIELDPREFIX_NEW"."SQL_SYSFIELD1" <> -1 \
    and "SQL_FIELDPREFIX_OLD"."SQL_SYSFIELD2" = 0) \
then \
    "SQL_FIELDPREFIX_NEW"."SQL_SYSFIELD2" := "SQL_FIELDPREFIX_NEW"."SQL_SYSFIELD1"; \
end if; \
RETURN NEW; \
END; \
\$BODY\$ \
LANGUAGE 'plpgsql'"

#define SQL_TRIGGER_BU1 "CREATE TRIGGER %s_sync_bu1 \
BEFORE UPDATE on %s.%s \
FOR EACH ROW \
EXECUTE PROCEDURE %s.%s_sync_func_bu1()"

// Триггер для отслеживания изменения записи.
// Триггер должен срабатывать после всех триггеров,
// вызываемых до изменения записи и только в случае,
// если изменились значения любых полей, кроме
// служебных (другими словами, изменение значения
// любого из служебных полей не должно приводить
// к вызыву данного триггера).

#define SQL_FUNCTION_BU2 "CREATE OR REPLACE FUNCTION %s.%s_sync_func_bu2() \
RETURNS trigger AS \
\$BODY\$ \
BEGIN \
if ("SQL_FIELDPREFIX_NEW"."SQL_SYSFIELD1" = "SQL_FIELDPREFIX_OLD"."SQL_SYSFIELD1" \
    and "SQL_FIELDPREFIX_NEW"."SQL_SYSFIELD2" = "SQL_FIELDPREFIX_OLD"."SQL_SYSFIELD2") \
then \
    "SQL_FIELDPREFIX_NEW"."SQL_SYSFIELD1" := 0; \
end if; \
RETURN NEW; \
END; \
\$BODY\$ \
LANGUAGE 'plpgsql'"

#define SQL_TRIGGER_BU2 "CREATE TRIGGER %s_sync_bu2 \
BEFORE UPDATE on %s.%s \
FOR EACH ROW \
EXECUTE PROCEDURE %s.%s_sync_func_bu2()"

// SQL-скрипт создания таблицы для удаленных записей
// Параметры шаблона:
//   owner          - владелец таблицы
//   table          - имя таблицы
//   all_fields_def - определения всех полей таблицы (кроме служебных полей)
//                    из удаленной БД, перечисленные через запятую

#define SQL_WATCHDELETE_TABLE "CREATE TABLE %s.%s_"SQL_PREFIX_DELTABLE" ( \
%s, \
"SQL_SYSFIELD1" integer DEFAULT 0 NOT NULL, \
"SQL_SYSFIELD2" integer DEFAULT 0 NOT NULL)"

// SQL-скрипт создания триггера на удаление записи
// Параметры шаблона:
//   owner          - владелец таблицы
//   table          - имя таблицы
//   all_fields     - список всех полей таблицы (кроме служебных полей),
//                    перечисленных через запятую
//   all_values     - список всех полей таблицы (кроме служебных полей)
//                    в виде:
//                      old_values.<field1>,
//                      old_values.<field2>,
//                      ...
//                      old_values.<fieldN>

#define SQL_WATCHDELETE_FUNCTION "CREATE OR REPLACE FUNCTION %s.%s_sync_func_ad() \
RETURNS trigger AS \
\$BODY\$ \
BEGIN \
if ("SQL_FIELDPREFIX_OLD"."SQL_SYSFIELD2" <> 0) then \
    INSERT INTO %s.%s_"SQL_PREFIX_DELTABLE"( \
        %s, \
        "SQL_SYSFIELD1", \
        "SQL_SYSFIELD2") \
    VALUES( \
        %s, \
        0, \
        "SQL_FIELDPREFIX_OLD"."SQL_SYSFIELD2"); \
end if; \
RETURN NULL; \
END; \
\$BODY\$ \
LANGUAGE 'plpgsql'"

#define SQL_WATCHDELETE_TRIGGER "CREATE TRIGGER %s_sync_ad \
AFTER DELETE on %s.%s \
FOR EACH ROW \
EXECUTE PROCEDURE %s.%s_sync_func_ad()"


// todo SQLCOLUMNS
// Вернуть список всех полей определенной таблицы исключая системные поля.
// Список должен быть отсортирован по названию полей - сначала ключевые
// поля, затем все остальные.

#define SQL_FIELDLIST " \
SELECT t1.column_name \
  FROM information_schema.columns AS t1 \
    LEFT JOIN information_schema.key_column_usage AS t2 \
           ON t1.table_schema = t2.table_schema \
          AND t1.table_name = t2.table_name \
          AND t1.column_name = t2.column_name \
    LEFT JOIN information_schema.table_constraints AS t3 \
           ON t2.table_schema = t3.table_schema \
          AND t2.table_name = t3.table_name \
          AND t2.constraint_name = t3.constraint_name \
 WHERE t1.table_schema = "SQL_UPPER"('%s') \
   AND t1.table_name = "SQL_UPPER"('%s') \
   AND t1.column_name NOT IN ('"SQL_SYSFIELD1"', '"SQL_SYSFIELD2"') \
   AND t1.column_name NOT LIKE 'pkt_%%' \
 GROUP BY t1.column_name \
 ORDER BY sum(case when t3.constraint_type = 'PRIMARY KEY' then 1 else 0 end) desc, t1.column_name"

// todo SQLCOLUMNS
// Вернуть список ключевых или неключевых полей определенной
// таблицы исключая системные поля.
// Список должен быть отсортирован по названию полей.

#define SQL_FIELDLIST_EX " \
SELECT t1.column_name \
  FROM information_schema.columns AS t1 \
    LEFT JOIN information_schema.key_column_usage AS t2 \
           ON t1.table_schema = t2.table_schema \
          AND t1.table_name = t2.table_name \
          AND t1.column_name = t2.column_name \
    LEFT JOIN information_schema.table_constraints AS t3 \
           ON t2.table_schema = t3.table_schema \
          AND t2.table_name = t3.table_name \
          AND t2.constraint_name = t3.constraint_name \
 WHERE t1.table_schema = "SQL_UPPER"('%s') \
   AND t1.table_name = "SQL_UPPER"('%s') \
   AND t1.column_name NOT IN ('"SQL_SYSFIELD1"', '"SQL_SYSFIELD2"') \
   AND t1.column_name NOT LIKE 'pkt_%%' \
  GROUP BY t1.column_name \
  HAVING sum(case when t3.constraint_type = 'PRIMARY KEY' then 1 else 0 end) <equal_or_not> $SQL_FIELD_IN_PKEY \
  ORDER BY t1.column_name"

// todo SQLCOLUMNS
// Вернуть список всех служебных полей определенной таблицы

#define SQL_FIELDLIST_SPECIAL " \
SELECT t1.column_name \
  FROM information_schema.columns AS t1 \
 WHERE t1.table_schema = "SQL_UPPER"('<owner>') \
   AND t1.table_name = "SQL_UPPER"('<table>') \
   AND t1.column_name LIKE 'pkt_%%' \
 ORDER BY t1.column_name"

// todo
// Список метаданных о полях определенной таблицы исключая системные поля.
// Список должен состоять из строк с пятью полями, разделенных пробелом:
//   <имя поля> <входит в перв.ключ?> <код типа поля> <размер1> <размер2>
// и быть отсотрированным по названию полей - сначала ключевые поля,
// затем все остальные.

#define SQL_FIELDLIST_META " \
SELECT column_name, sum(ord), data_type, data_precision, numeric_scale \
FROM ( \
  SELECT t1.column_name, \
         case when t3.constraint_type = 'PRIMARY KEY' then 1 else 0 end ord, \
         lower(replace(replace(t1.data_type,' ','_'),'-','_')) data_type, \
         case when t1.data_type like 'character%%' \
                 then coalesce(t1.character_maximum_length,1024) \
              when t1.data_type = 'uuid' \
                 then 36 \
              when t1.data_type = 'USER-DEFINED' \
                 then 1024 \
              else COALESCE(t1.numeric_precision,t1.datetime_precision) \
         end data_precision, \
         COALESCE(0,t1.numeric_scale) numeric_scale \
    FROM information_schema.columns AS t1 \
      LEFT JOIN information_schema.key_column_usage AS t2 \
             ON t1.table_schema = t2.table_schema \
            AND t1.table_name = t2.table_name \
            AND t1.column_name = t2.column_name \
      LEFT JOIN information_schema.table_constraints AS t3 \
             ON t2.table_schema = t3.table_schema \
            AND t2.table_name = t3.table_name \
            AND t2.constraint_name = t3.constraint_name \
   WHERE t1.table_schema = "SQL_UPPER"('%s') \
     AND t1.table_name = "SQL_UPPER"('%s') \
     AND t1.column_name NOT IN ('"SQL_SYSFIELD1"', '"SQL_SYSFIELD2"') \
) T \
GROUP BY column_name, data_type, data_precision, numeric_scale \
ORDER BY sum(ord) desc, column_name"

// Вернуть что-либо, если указанная таблица является
// представлением (view)

#define SQL_ISVIEW "SELECT 1 FROM information_schema.tables \
WHERE table_schema = "SQL_UPPER"('<owner>') \
    AND table_name = "SQL_UPPER"('<table>') \
    AND table_type = 'VIEW'"

// ( rc == SQL_SUCCESS ) || ( rc == SQL_SUCCESS_WITH_INFO )
//#define SQL_SUCCEEDED(rc) (((rc)&(~1))==0)

extern SQLHENV  env;
extern SQLHDBC  dbc;

#define EXEC_SQL(x) \
    retcode = x;\
    if ( !( retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO ) ) {\
        syncctl_error( stmt , __FILE__, __LINE__ );\
        SQLFreeStmt( stmt, SQL_DROP );\
        return -1;\
    }

#define EXEC_SQL1(x) \
    retcode1 = x;\
    if ( !( retcode1 == SQL_SUCCESS || retcode1 == SQL_SUCCESS_WITH_INFO ) ) {\
        syncctl_error( stmt1 , __FILE__, __LINE__ );\
        SQLFreeStmt( stmt1, SQL_DROP );\
        return -1;\
    }
